package com.flynn.rock.system.service.impl;

import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.DES;
import com.flynn.rock.enums.system.NoIdentifierType;
import com.flynn.rock.enums.system.UserAccountStatus;
import com.flynn.rock.enums.system.UserAccountType;
import com.flynn.rock.model.PageResult;
import com.flynn.rock.model.dto.system.AuthorityDto;
import com.flynn.rock.model.dto.system.RoleDto;
import com.flynn.rock.model.dto.system.UserAccountDto;
import com.flynn.rock.model.dto.system.UserDto;
import com.flynn.rock.system.convert.AbstractRoleConvert;
import com.flynn.rock.system.convert.AbstractUserConvert;
import com.flynn.rock.system.entity.SysRoleAuthorityRelationEntity;
import com.flynn.rock.system.entity.SysUserAccountEntity;
import com.flynn.rock.system.entity.SysUserEntity;
import com.flynn.rock.system.entity.SysUserRoleRelationEntity;
import com.flynn.rock.system.model.QueryUserCriteria;
import com.flynn.rock.system.model.dto.SaveUserDto;
import com.flynn.rock.system.model.dto.SysUserDto;
import com.flynn.rock.system.model.vo.ListUserVo;
import com.flynn.rock.system.model.vo.LoginUserVo;
import com.flynn.rock.system.model.vo.UserDetailVo;
import com.flynn.rock.system.model.vo.UserIdVo;
import com.flynn.rock.system.repository.SysAuthorityRepository;
import com.flynn.rock.system.repository.SysRoleAuthorityRelationRepository;
import com.flynn.rock.system.repository.SysRoleRepository;
import com.flynn.rock.system.repository.SysUserAccountRepository;
import com.flynn.rock.system.repository.SysUserRepository;
import com.flynn.rock.system.repository.SysUserRoleRelationRepository;
import com.flynn.rock.system.service.SysNoIdentifierService;
import com.flynn.rock.system.service.SysUserService;
import com.flynn.rock.util.JpaQueryHelper;
import com.flynn.rock.util.PageUtil;
import com.flynn.rock.util.SecurityUtil;
import jakarta.annotation.Resource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

@Service
public class SysUserServiceImpl implements SysUserService, UserDetailsService {

    private static final DES des = SecureUtil.des("beworthKey2022".getBytes(StandardCharsets.UTF_8));

    @Resource
    private SysNoIdentifierService sysNoIdentifierService;

    @Resource
    private SysUserRepository sysUserRepository;

    @Resource
    private SysRoleRepository sysRoleRepository;

    @Resource
    private SysAuthorityRepository sysAuthorityRepository;

    @Resource
    private SysUserAccountRepository sysUserAccountRepository;

    @Resource
    private SysUserRoleRelationRepository sysUserRoleRelationRepository;

    @Resource
    private SysRoleAuthorityRelationRepository sysRoleAuthorityRelationRepository;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUserAccountEntity sysUserAccountEntity = sysUserAccountRepository.selectByUsername(username);
        if (Objects.isNull(sysUserAccountEntity)) {
            throw new UsernameNotFoundException(username);
        }
        SysUserEntity userEntity = sysUserRepository.findById(sysUserAccountEntity.getUserId()).orElseThrow();
        List<Long> roleIds = sysUserRoleRelationRepository.findAllByUserId(userEntity.getUserId()).stream()
                .map(SysUserRoleRelationEntity::getRoleId)
                .toList();
        List<RoleDto> roles = sysRoleRepository.findAllById(roleIds).stream()
                .map(AbstractRoleConvert.INSTANCE::toRoleDto)
                .toList();
        List<Long> authorityIds = sysRoleAuthorityRelationRepository.findAllByRoleIdIn(roleIds).stream()
                .map(SysRoleAuthorityRelationEntity::getAuthorityId)
                .toList();
        List<AuthorityDto> authorityDtoList = sysAuthorityRepository.findAllById(authorityIds).stream()
                .map(AbstractUserConvert.INSTANCE::toAuthorityDto)
                .toList();
        UserDto userDto = new UserDto();
        userDto.setUserId(userEntity.getUserId());
        userDto.setDeptId(userEntity.getDeptId());
        userDto.setRealName(userEntity.getRealName());
        userDto.setStatus(userEntity.getStatus());
        return UserAccountDto.builder()
                .userId(userEntity.getUserId())
                .account(sysUserAccountEntity.getAccount())
                .password(sysUserAccountEntity.getPassword())
                .accountType(sysUserAccountEntity.getAccountType())
                .accountStatus(sysUserAccountEntity.getStatus())
                .authorities(authorityDtoList)
                .roles(roles)
                .user(userDto)
                .build();
    }

    @Transactional
    @Override
    public UserIdVo saveUser(SaveUserDto user) {
        SysUserEntity userEntity = AbstractUserConvert.INSTANCE.toSaveEntity(user);
        if (Objects.isNull(user.getUserId())) {
            userEntity.setUserCode(sysNoIdentifierService.getNoIdentifier(NoIdentifierType.USER_CODE));
        }
        userEntity = sysUserRepository.save(userEntity);
        SysUserRoleRelationEntity roleRelationEntity = sysUserRoleRelationRepository.findByUserIdAndRoleId(userEntity.getUserId(),
                user.getRoleId());
        if (Objects.isNull(roleRelationEntity)) {
            roleRelationEntity = new SysUserRoleRelationEntity();
            roleRelationEntity.setUserId(userEntity.getUserId());
            roleRelationEntity.setRoleId(user.getRoleId());
            sysUserRoleRelationRepository.save(roleRelationEntity);
        }
        SysUserAccountEntity sysUserAccountEntity = sysUserAccountRepository.selectByUsername(user.getPhone());
        if (Objects.isNull(sysUserAccountEntity)) {
            sysUserAccountEntity = new SysUserAccountEntity();
            sysUserAccountEntity.setAccount(user.getPhone());
            String password = des.decryptStr(user.getPassword());
            sysUserAccountEntity.setPassword(passwordEncoder.encode(password));
            sysUserAccountEntity.setAccountType(UserAccountType.PHONE_ACCOUNT);
            sysUserAccountEntity.setStatus(UserAccountStatus.NORMAL);
            sysUserAccountEntity.setUserId(userEntity.getUserId());
            sysUserAccountRepository.save(sysUserAccountEntity);
        } else if (StringUtils.hasText(user.getPassword())) {
            String password = des.decryptStr(user.getPassword());
            sysUserAccountEntity.setPassword(passwordEncoder.encode(password));
            sysUserAccountRepository.save(sysUserAccountEntity);
        }
        return UserIdVo.builder()
                .userId(userEntity.getUserId())
                .userCode(userEntity.getUserCode())
                .build();
    }

    @Override
    public Optional<SysUserDto> getUserByPhone(String phone) {
        return Optional.ofNullable(sysUserRepository.findByPhone(phone))
                .map(AbstractUserConvert.INSTANCE::toSysUserDto);
    }

    @Override
    public PageResult<ListUserVo> pageUser(QueryUserCriteria criteria, Pageable pageable) {
        Page<SysUserEntity> page = sysUserRepository.findAll(
                ((root, query, criteriaBuilder) -> JpaQueryHelper.getPredicate(root, criteria, criteriaBuilder)),
                pageable
        );
        return PageUtil.toPage(page.map(AbstractUserConvert.INSTANCE::toListUserVo));
    }

    @Override
    public UserDetailVo getUser(Long userId) {
        SysUserEntity userEntity = sysUserRepository.findById(userId).orElseThrow();
        return AbstractUserConvert.INSTANCE.toUserDetailVo(userEntity);
    }

    @Override
    public LoginUserVo getLoginUserInfo() {
        UserAccountDto currentUser = SecurityUtil.getCurrentUser();
        UserDto user = currentUser.getUser();
        return LoginUserVo.builder()
                .userId(user.getUserId())
                .account(currentUser.getAccount())
                .authorities(currentUser.getAuthorities())
                .roles(currentUser.getRoles())
                .deptId(user.getDeptId())
                .realName(user.getRealName())
                .status(user.getStatus())
                .build();
    }

    @Transactional
    @Override
    public void deleteUser(Long userId) {
        sysUserRoleRelationRepository.deleteAllByUserId(userId);
        sysUserAccountRepository.deleteAllByUserId(userId);
        sysUserRepository.deleteById(userId);
    }
}
